objective_c 中__weak的实现原理

weak引用表,是一张hash表。key为对象的地址,value为所有指向该对象的弱引用指针。

  • 实现过程,objc_initWeak
  • SideTable
  • weak使用注意事项:如果大量使用weak修饰符修饰的变量是,最好先赋值给__strong修饰符变量再使用。至于原因下文会详细说明。
  • weak引用表的释放

概况

1
2
id obj = [NSObject new];
id __weak obj1 = obj ;

编译器编译后:

obj;
1
2
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

通过objc_initWeak 初始化objc1变量,变量objc1超出作用于后通过objc_destroyWeak释放objc1.

objc_initWeak初始化

1
2
3
4
5
6
7
8
9
10
id objc_initWeak(id *location, id newObj) 
{
//首先判断newObj是否为nil,是则直接返回。
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}

storeWeak函数 Update a weak variable. 更新弱引用变量,忽略多线程资源竞争逻辑,主要分为三步:

  1. 声明新旧两个SideTabel对象,分别代表旧的weak引用表与新的weak引用表
  2. 如果weak引用已经有所指向,则将weak引用从旧对象的weak引用表中清除
  3. 如果weak引用有新的指向,则将weak引用的注册到新对象的weak引用表中

haveOld 弱引用是否已经有所指向
haveNew 是否有新的指向
CrashIfDeallocating 执行方法时发生Deallocate是否Crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);

Class previouslyInitializedClass = nil;
id oldObj;
// 声明旧的弱引用表 与 新的弱引用表
SideTable *oldTable;
SideTable *newTable;

// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}

SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}

// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));

// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;

goto retry;
}
}

// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected

// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}

// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}

SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

return (id)newObj;
}

objc_destroyWeak 函数,

1
2
3
4
5
6
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}

weak引用表的释放

先上一张图,一图胜千言

objc_object::clearDeallocating()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));

//1、去到weak引用表
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
//2、清除所有的weak引用
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}

来看看weak_clear_no_lock函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 1、从weak表中,获取referent_id的 weak引用实体 也就是weak_entry_t

weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}

// zero out references
weak_referrer_t *referrers;
size_t count;

// 2、获取weak_entry_t中的referrers, 也就是所有引用了referent_id 的弱引用对象,这是一个数组
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
// 3、遍历数组,将所有弱引用对象赋值为 nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
// 4、将entry从weak引用表中移除
weak_entry_remove(weak_table, entry);
}

这就很清楚了,主要步骤:

  1. 从weak表中,获取referent_id的 weak引用实体 也就是weak_entry_t
  2. 获取weak_entry_t中的referrers, 也就是所有引用了referent_id 的弱引用对象,这是一个数组
  3. 遍历数组,将所有弱引用对象赋值为 nil
  4. 将entry从weak引用表中移除

回答刚开始提出的问题:为什么如果大量使用weak修饰符修饰的变量时,最好先赋值给strong修饰符变量再使用?

因为weak修饰符修饰的变量生命周期分为:创建、存储、销毁三个过程,其中还涉及Hash以及数组的遍历等操作。而strong修饰符只是简单引用计数+1和-1,相对于strong修饰符来说weak修饰符更消耗性能。

以上不是主要原因,因为使用weak修饰符修饰的变量时,即是使用注册的autoreleasepool中的变量,这是为了确保weak变量在使用过程中,不会被置为nil。而如果大量使用weak变量的话,就会导致weak变量多次注册到autoreleasepool,为了避免这种情况,最好先赋值给__strong 变量再使用